package com.kit.lib;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;


/**
 * @Description: 随机密码帮助类，时间统一使用北京时间
 * @Author: zhouzong,yuanqiyong
 * @CreateDate: 2019-07-23 18:39
 * @Version: 1.0
 * @Copyright COPYRIGHT © 2018 联寓智能.沪ICP备 16013189号-1.
 */
public class RandomCipherHelper {
	final static int HIGH_CODE_LENGTH = 87616;// 高位编码长度
	final static int LOW_CODE_LENGTH = 97536;// 低位编码长度

	// final static int LOW_COE_REPETITION_NUM = 10;// 低位编码副本数量
	final static int HIGH_NUM_LENGTH = 5;// 高位编码位数
	final static String HIGH_NUM_PREFIX = "0";// 高位编码前缀
	// =========================================
	static List<Integer> sequenceHigh = new ArrayList<>();
	static List<Integer> sequenceLow = new ArrayList<>();
	/**
	 * 该初始化在门锁初始化时执行
	 */
	static {
		// == 初始化密码数组 =======================================
		int seed1 = 100000019;// 种子1
		RandomSequence seq1 = new RandomSequence(seed1);
		sequenceHigh = seq1.getSequence(HIGH_CODE_LENGTH);

		int seed2 = 100000019;// 种子2
		RandomSequence seq2 = new RandomSequence(seed2);
		sequenceLow = seq2.getSequence(LOW_CODE_LENGTH);
	}

	public static void main(String[] args) throws InterruptedException {
		// == 必要参数 =======================================
		int initHour = getLocalTimeHour(new Date(1546272000000L)); // 基准时间点2019-1-1

		int startHour = getLocalTimeHour(new Date(1565286000000L)); // 选一个密码开始时间
		int endHour = getLocalTimeHour(new Date()) + 10; // 选一个密码结束时间[只在常规密码下有效]
		int type = 5, loopStrategy = 8, loopEndPoint = 21;// 策略，

		// == 生成密码 =======================================
		String pwdStart = computHighPwd(initHour, startHour);
		System.out.println("H:" + pwdStart);
		List<Integer> pwdends = computLowPwd(startHour, endHour, type, loopStrategy, loopEndPoint);
		System.out.println("L:" + pwdends + "\t" + pwdends.size());
		int index = (int) (Math.random() * pwdends.size());// 随便取一个
		Integer pwdEnd = pwdends.get(index);
		String pwd = pwdStart + "" + pwdEnd;
		System.out.println("生成密码:" + pwd);

		// == 密码加密 =======================================
		AES10 aes10 = new AES10("1234567890123456".toCharArray(), "1234567890123456".toCharArray());
		String aesPwd = new String(aes10.Encrypt(pwd.toCharArray(), pwd.length()));// 加密后
		String pwd2 = new String(aes10.Decrypt(aesPwd.toCharArray(), aesPwd.length()));// 解密后
		System.out.println("AES密码 :" + aesPwd + "\t解密后：" + pwd2);

		// == 校验密码 =======================================
		// 检验密码的操作只发生在硬件端，java端不需要
		System.out.println("## 当前时间：" + new Date());
		loadPwds();
		{
			int p1 = (sequenceHigh.get(Integer.parseInt(pwdStart)) + initHour); // 开始时间：到小时
			int p2 = sequenceLow.get(pwdends.get(index)) / 10; // 结束时间：小时偏移量
			int nowHour = getLocalTimeHour(new Date());
			if (doParse(p1, p2, nowHour, pwd2) > 0)
				System.err.println("\n密码验证成功");
			else
				System.err.println("\n密码验证失败");
		}
		savePwds();
	}

	private static int getLocalTimeHour(Date localDate) {
		long t = (localDate.getTime() / 1000 / 60 / 60);
		return (int) t;
	}

	/**
	 * 计算高位密码
	 * 
	 * @param startHour 密码开始时间点
	 * @return 下标，供c端使用
	 */
	public static String computHighPwd(int initHour, int startHour) {
		int offset = startHour - initHour;
		int index = sequenceHigh.indexOf(offset);
		return leftPad(Integer.toString(index), HIGH_NUM_LENGTH, HIGH_NUM_PREFIX);
	}

	/**
	 * 计算低位密码(返回可选的集合列表,调用者随机取出一个没有使用过的密码)
	 *
	 * @param startHour    密码开始时间点
	 * @param endTime      密码结束时间[只有常规密码时，才有结束时间]
	 * @param type         随机密码功能类型(1常规密码,2单次密码,3永久密码,4清空密码,5循环密码)
	 * @param loopStrategy 循环策略(1周一循环,2周二循环,3周三循环,4周四循环,5周五循环,
	 *                     6周六循环,7周天循环,8工作日循环,9周末循环,10每日循环)
	 * @param loopEndPoint 循环结束时间点
	 * @return
	 */
	public static List<Integer> computLowPwd(int startHour, int endHour, int type, int loopStrategy, int loopEndPoint) {
		Integer[] interval = getInterval(startHour, endHour, type, loopStrategy, loopEndPoint);
		return indexsOf(interval);
	}

	private static Integer[] getInterval(int startHour, int endHour, int type, int loopStrategy, int loopEndPoint) {
		if (type == 1) {
			int offset = endHour - startHour;
//			return new Integer[] { offset <= 8760 ? offset : ((offset - 8760) / (30 * 24) + 1 + 8760) };
			int offsetM = compareTo(startHour, endHour);
			return new Integer[] { offset <= 8760 ? offset : 8760 + offsetM }; // 超过一年，使用月份
		} else if (type == 2) {
			return new Integer[] { 8989, 9288 };
		} else if (type == 3) {
			return new Integer[] { 9299, 9498 };
		} else if (type == 4) {
			return new Integer[] { 9499, 9508 };
		} else if (type == 5) {
			return new Integer[] { 9509 + (loopStrategy - 1) * 24 + loopEndPoint };
		}
		return null;
	}

	/**
	 * 获取目标数组在源数组中的下标列表
	 * 
	 * @param targetArr 如果一个值就是当前值，如果多个值就是区间
	 * @return
	 */
	private static List<Integer> indexsOf(Integer... targetArr) {
		int start = targetArr[0];
		int end = targetArr[targetArr.length - 1];
		List<Integer> indexs = new ArrayList<>();
		for (int i = 0; i < sequenceLow.size(); i++) {// 10倍数组,只遍历一遍
			int x = sequenceLow.get(i) / 10;// 偏移量。10份
			if (start <= x && end >= x) // 区间内
				indexs.add(i);
		}
		return indexs;
	}

	/** 数字左边补0 ,param:要补0的数， length:补0后的长度 */
	private static String leftPad(String param, int len, String def) {
		int l = param.length();
		String ret = "";
		while (l++ < len)
			ret += def;
		return ret + param;
	}

	// ====================================================================
	// === 以下代码为:模拟解析 密码 ==========================================
	// === 真实业务环境中，不会使用下面的代码 ================================
	// ====================================================================

	/**
	 * 解析 密码
	 * 
	 * @param p1      高位：开始时间hour
	 * @param p2      低位：偏移量
	 * @param nowHour 当前时间hour
	 * @param pwd     输入的密码
	 * @return 校验结果
	 */
	public static int doParse(int p1, int p2, int nowHour, String pwd) {
		Date day1 = (new Date(p1 * 1000L * 60L * 60L));
		System.err.println(">> 开始时间：" + (day1));

		int uselog = existPwd(pwd); // 检查历史成功密码是否存在。TODO
		Assert.isFalse(nowHour < p1, "密码开始时间不在有效期内");
		Assert.isFalse(uselog == 0 && Math.abs(nowHour - p1) > 24, "密码未在24小时内使用，已经作废");

		// =======================================================
		if (p2 <= 8760) // 1常规密码,
		{
			System.err.println(">> 常规密码1");
			Date day2 = (new Date((p1 + p2) * 1000L * 60L * 60L));
			System.err.println(">> 结束时间：" + day2);
			Assert.isFalse(p1 + p2 < nowHour, "密码结束时间不在有效期内");
		} else if (p2 > 8760 && p2 <= 8988) // 1常规密码,
		{
			System.err.println(">> 常规密码2");
			Date dayn = new Date();
			int offsetM = compareTo(day1, dayn);// 获取真实月份差值
			// 密码月差值 >=真实月差值 && ...
			boolean bo = (p2 - 8760 >= offsetM) && (day1.getDate() >= dayn.getDate()) && (day1.getHours() >= dayn.getHours());// 能否开门
			Assert.isFalse(!bo, "密码结束时间不在有效期内");
//			int x = (p2 - 8760 - 1) * (30 * 24) + 8760 + p1;
//			Assert.isFalse(x < nowHour, "密码结束时间不在有效期内");
		} else if (p2 > 8988 && p2 <= 9288) // 2单次密码,
		{
			System.err.println(">> 单次密码");
			Assert.isFalse(uselog != 0, "密码使用过。作废");
		} else if (p2 > 9288 && p2 <= 9498) // 3永久密码,
		{
			System.err.println(">> 永久密码");
			// return 1;
		} else if (p2 > 9498 && p2 <= 9508) // 4清空密码,
		{
			System.err.println(">> 清空密码");
			clearPwd(); // 清空所有历史密码 TODO
			return 0; // 直接返回，不存储
		} else if (p2  >= 9509) // 5循环密码
		{
			int loopStrategy = (p2 - 9509) / 24 + 1; // 循环策略(1周一循环,2周二循环,3周三循环,4周四循环,5周五循环,6周六循环,7周天循环,8工作日循环,9周末循环,10每日循环)
			int loopEndPoint = (p2 - 9509) % 24; // 结束小时

			System.err.println(">> 循环密码：");
			System.err.println(">> 策略：" + WEEKS.get(loopStrategy).name);
			System.err.println(">> 结束时间，当天：" + loopEndPoint + "点");

			Date local = (new Date());
			int week = getWeek(local); // 星期 – 取值区间为[0,6]，其中0代表星期天，1代表星期一
			int hour = getHour(local); // 时 - 取值区间为[0,23]
			week = week == 0 ? 7 : week;

			loopEndPoint = loopEndPoint == 0 ? 24 : loopEndPoint; // 纠正
			int beginPoint = (p1 + 8) % 24;
			System.out.println("beginPoint:" + beginPoint + "\t nowPoint:" + hour + "\tendPoint:" + loopEndPoint);
			if (!((loopStrategy == week || (loopStrategy == 8 && week <= 5) || (loopStrategy == 9 && week >= 6)
					|| loopStrategy == 10) && (loopEndPoint > hour && beginPoint <= hour)))
				return 0;
		}
		putPwd(pwd); // 存储成功的密码 TODO
		return 1;
	}

	private static Set<String> pwds = new HashSet<>();

	private static int existPwd(String pwd) {
		return pwds.contains(pwd) ? 1 : 0;
	}

	private static void putPwd(String pwd) {
		pwds.add(pwd);
	}

	private static void clearPwd() {
		pwds.clear();
	}

	private static int getWeek(Date date) {
		Calendar cal = Calendar.getInstance();
		cal.setTime(date);
		return cal.get(Calendar.DAY_OF_WEEK) - 1;
	}

	private static int getHour(Date date) {
		Calendar cal = Calendar.getInstance();
		cal.setTime(date);
		return cal.get(Calendar.HOUR_OF_DAY);
	}

	public static enum WEEKS {
		W1(1, "周一循环"), W2(2, "周二循环"), W3(3, "周三循环"), W4(4, "周四循环"), W5(5, "周五循环"), W6(6, "周六循环"), W7(7, "周天循环"),
		W8(8, "工作日循环"), W9(9, "周末循环"), W10(10, "每日循环");

		int i;
		String name;

		private WEEKS(int i, String name) {
			this.i = i;
			this.name = name;
		}

		public static WEEKS get(int i) {
			for (WEEKS w : WEEKS.values()) {
				if (w.i == i)
					return w;
			}
			return null;
		}
	}

	private static void loadPwds() {
		try {
			File f = new File("/PWDS.txt");
			if (f.exists()) {
				byte[] context = new byte[(int) f.length()];
				FileInputStream is = new FileInputStream(f);
				is.read(context);
				is.close();
				pwds.addAll(Arrays.asList(new String(context).replaceAll("\\s*", "").split(",")));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private static void savePwds() {
		try {
			if (!pwds.isEmpty()) {
				File f = new File("/PWDS.txt");
				StringBuilder sb = new StringBuilder();
				for (String str : pwds)
					if (!str.isEmpty())
						sb.append(str + ",\n");
				FileOutputStream os = new FileOutputStream(f);
				os.write(sb.substring(0, sb.length() - 1).getBytes());
				os.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public abstract static class Assert {
		public static void isFalse(boolean expression, String message) {
			if (expression) {
				throw new IllegalArgumentException(message);
			}
		}
	}

	// == ================================================

	/**
	 * 计算两个时间的月份差值
	 * 
	 * @param hour1 小
	 * @param hour2 大
	 * @return
	 */
	public static int compareTo(int hour1, int hour2) {
		Date day1 = new Date(hour1 * 60 * 60 * 1000L);
		Date day2 = new Date(hour2 * 60 * 60 * 1000L);
		return compareTo(day1, day2);
	}

	public static int compareTo(Date day1, Date day2) {
		return (day2.getYear() - day1.getYear()) * 12 + (day2.getMonth() - day1.getMonth());
	}
}
